import { Router, RequestHandler } from 'express'

export { Request as ExpressRequest, Response as ExpressResponse, NextFunction } from 'express'

// 数据格式
export enum DataFormat {
  X_WWW_FORM_URLENCODED = 'application/x-www-form-urlencoded',
  FORMDATA = 'multipart/form-data',
  FORMDATA_CUSTOM = 'multipart/form-data ',
  JSON = 'application/json',
  PLAIN = 'text/plain'
}

export interface Meta {
  baseUrl: string
  routes: { [key: string]: MethodMeta }
  midwares: (Router | RequestHandler)[]
}

export interface MethodMeta {
  subUrl: string
  httpMethod: string
  midwares: (Router | RequestHandler)[]
  params: ParamMeta[]
  isCustom: boolean
  dataFormat: DataFormat
}

export type HttpMethod = 'post' | 'get' | 'put' | 'delete'

export interface ParamTypeMap {
  request: Function
  response: Function
  query: Function
  body: Function
  cookie: Function
  header: Function
  params: Function
  next: Function
}

export type ParamType = keyof ParamTypeMap

export interface ParamMeta {
  paramIndex: number
  paramType: ParamType
  paramName: string
}

export function getMeta(constructor: Function): Meta {
  return constructor.prototype.$META || (constructor.prototype.$META = {
    baseUrl: '',
    routes: {},
    midwares: []
  } as Meta)
}

export function getMethodMeta(target: Object, methodName: string): MethodMeta {
  const meta = getMeta(target.constructor)
  const routes = meta.routes
  return routes[methodName] || (routes[methodName] = {
    subUrl: '',
    httpMethod: '',
    midwares: [],
    params: [],
    isCustom: false,
    dataFormat: DataFormat.JSON
  } as MethodMeta)
}

export function Controller(baseUrl: string, ...midwares: Array<Router | RequestHandler>) {
  if (!baseUrl.startsWith('/')) {
    throw new Error('请输入 / 开头的路径')
  }

  return function(constructor: Function) {
    const meta = getMeta(constructor)
    meta.baseUrl = baseUrl
    meta.midwares = midwares
  }
}

export interface MethodHandler {
  (target: Object, methodName: string): void
}

export interface MethodFactoryHandler {
  (subUrl: string, ...midwares: Array<Router | RequestHandler>): MethodHandler
  (subUrl: string, dataFormat: DataFormat, ...midwares: Array<Router | RequestHandler>): MethodHandler
}

export function MethodFactory(httpMethod: HttpMethod, isCustom = false): MethodFactoryHandler {
  return function (subUrl: string, temp: any, ...midwares: Array<Router | RequestHandler>) {
    if (!subUrl.startsWith('/')) {
      throw new Error('请输入 / 开头的路径')
    }

    let dataFormat = DataFormat.JSON

    if (temp && Object.values(DataFormat).includes(temp)) {
      dataFormat = temp
    } else if (temp) {
      midwares.unshift(temp)
    }

    return function (target: Object, methodName: string) {
      const mehtodMeta = getMethodMeta(target, methodName)
      mehtodMeta.subUrl = subUrl
      mehtodMeta.httpMethod = httpMethod
      mehtodMeta.midwares = midwares
      mehtodMeta.isCustom = isCustom
      mehtodMeta.dataFormat = dataFormat
    }
  }
}

export const Get = MethodFactory('get')
export const Post = MethodFactory('post')
export const Put = MethodFactory('put')
export const Delete = MethodFactory('delete')

export const CustomGet = MethodFactory('get', true)
export const CustomPost = MethodFactory('post', true)
export const CustomPut = MethodFactory('put', true)
export const CustomDelete = MethodFactory('delete', true)

export function getParamMeta(target: Object, methodName: string, paramIndex: number): ParamMeta {
  const mehtodMeta = getMethodMeta(target, methodName)
  return mehtodMeta.params[paramIndex] || (mehtodMeta.params[paramIndex] = {
    paramIndex,
    paramType: 'request',
    paramName: ''
  } as ParamMeta)
}

export function MethodContextFactory(paramType: ParamType) {
  return function (target: Object, methodName: string, paramIndex: number) {
    const paramMeta = getParamMeta(target, methodName, paramIndex)
    paramMeta.paramType = paramType
  }
}

export function MethodParamFactory(paramType: ParamType) {
  return function (paramName: string) {
    return function (target: Object, methodName: string, paramIndex: number) {
      const paramMeta = getParamMeta(target, methodName, paramIndex)
      paramMeta.paramType = paramType
      paramMeta.paramName = paramName
    }
  }
}

export const Request = MethodContextFactory('request')
export const Response = MethodContextFactory('response')
export const Query = MethodContextFactory('query')
export const Body = MethodContextFactory('body')
export const Cookie = MethodContextFactory('cookie')
export const Header = MethodContextFactory('header')
export const Params = MethodContextFactory('params')
export const Next = MethodContextFactory('next')

export const QueryParam = MethodParamFactory('query')
export const BodyParam = MethodParamFactory('body')
export const CookieParam = MethodParamFactory('cookie')
export const HeaderParam = MethodParamFactory('header')
export const ParamsParam = MethodParamFactory('params')